package driver

import (
	"context"
	"log/slog"
	"strconv"
	"time"

	"gitee.com/gousing/cache/internal"
	"gitee.com/gousing/cache/storage"
	"github.com/redis/go-redis/v9"
)

// redisKeyKinds Kind ZAdd有序集合映射管理
type redisKeyKinds struct {
	client     redis.UniversalClient
	shardCount uint32   // Keys 分片数量, 必须是2的幂次方, 用于哈希分片
	zsetKeys   []string // Redis ZAdd有序集合分片键名 , 用于存储键名KIND类型和过期时间
	ctx        context.Context
}

func newRedisKeyKinds(client redis.UniversalClient, prefixName string, shardCount uint32) *redisKeyKinds {
	if client == nil {
		panic("cache: newRedisKeyKinds(), client is nil")
	}
	shardCount = internal.PowerOf2(shardCount)
	rk := &redisKeyKinds{
		client:     client,
		shardCount: shardCount,
		zsetKeys:   make([]string, shardCount),
		ctx:        context.Background(),
	}
	for i := range shardCount {
		rk.zsetKeys[i] = prefixName + "shard:" + strconv.FormatUint(uint64(i), 10)
	}
	// 初始化 ZSET 键名, 不存在时创建, 并设置过期时间为0, 即永久有效
	for i := range shardCount {
		if typ, err := client.Type(redisTagCtx, rk.zsetKeys[i]).Result(); err != nil || typ != "zset" {
			// 可能由于键名已存在但不是 ZSET 类型, 例如是字符串等其他类型导致的错误
			// 删除后再试一次
			if _, err := client.Del(redisTagCtx, rk.zsetKeys[i]).Result(); err != nil {
				internal.GetLogger().Error("cache: newRedisKinds init error", slog.String("zsetKey", rk.zsetKeys[i]), slog.String("error", err.Error()))
			} else if _, err := client.ZAdd(redisTagCtx, rk.zsetKeys[i], redis.Z{
				Score:  0,
				Member: "<test>",
			}).Result(); err != nil {
				internal.GetLogger().Error("cache: newRedisKinds init error", slog.String("zsetKey", rk.zsetKeys[i]), slog.String("error", err.Error()))
			} else {
				client.ZRem(redisTagCtx, rk.zsetKeys[i], "<test>")
			}
		}
	}
	return rk
}

func (k *redisKeyKinds) ZsetKey(key string) string {
	return k.zsetKeys[internal.HashFnv32(key)&(k.shardCount-1)]
}

// Set 设置指定 Key 键名类型数据
func (k *redisKeyKinds) Set(key string, kind storage.Kind, expire time.Duration) error {
	if key == "" {
		return storage.ErrKeyIsEmpty
	}
	if kind == storage.KindInvalid || kind > 99 {
		return storage.ErrKeyKindInvalid
	}
	// 分数为float64整数: 过期时间戳*100 + Kind(最后2位表示 Kind 类型)
	var score float64
	if expire <= 0 {
		score = float64(kind)
	} else {
		score = (float64(time.Now().Unix())+expire.Seconds())*100 + float64(kind)
	}
	return k.client.ZAdd(k.ctx, k.ZsetKey(key), redis.Z{
		Score:  score,
		Member: key,
	}).Err()
}

// Get 获取指定 Key 键名类型数据
func (k *redisKeyKinds) Get(key string) (storage.Kind, error) {
	if key == "" {
		return storage.KindInvalid, storage.ErrKeyIsEmpty
	}
	val, err := k.client.ZScore(k.ctx, k.ZsetKey(key), key).Result()
	if err != nil {
		if err == redis.Nil {
			return storage.KindInvalid, storage.ErrKeyNotExists
		}
		return storage.KindInvalid, err
	}
	if val < 0 {
		return storage.KindInvalid, storage.ErrKeyNotExists
	}
	var (
		kind   storage.Kind
		expire int64
	)
	if val < 100 {
		kind = storage.Kind(int8(val))
	} else {
		expire = int64(val) / 100
		kind = storage.Kind(int64(val) % 100)
	}
	if expire > 0 && time.Now().Unix() >= expire {
		k.client.ZRem(k.ctx, k.ZsetKey(key), key) // 过期了，删除集合中的键名
		return storage.KindInvalid, storage.ErrKeyExpired
	}

	if kind == storage.KindInvalid {
		return storage.KindInvalid, storage.ErrKeyKindInvalid
	}
	return kind, nil
}

// Size 获取当前所有键名数量总和
func (k *redisKeyKinds) Size() int64 {
	var count int64 = 0
	for _, zsetKey := range k.zsetKeys {
		count += k.client.ZCard(k.ctx, zsetKey).Val()
	}
	return count
}

// Values 获取所有键名类型数据
func (k *redisKeyKinds) Values() (map[string]storage.Kind, error) {
	values := make(map[string]storage.Kind, k.Size())
	for _, zsetKey := range k.zsetKeys {
		data, err := k.client.ZRangeWithScores(k.ctx, zsetKey, 0, -1).Result()
		if err != nil {
			return map[string]storage.Kind{}, err
		}
		for _, val := range data {
			if val.Score < 100 {
				// 无过期时间，直接返回Kind类型
				values[val.Member.(string)] = storage.Kind(int8(val.Score))
			} else {
				// 有过期时间，取余数作为Kind类型
				expire := int64(val.Score) / 100
				if expire > 0 && time.Now().Unix() >= expire {
					k.client.ZRem(k.ctx, zsetKey, val.Member.(string)) // 过期了，删除集合中的键名
				} else {
					values[val.Member.(string)] = storage.Kind(int64(val.Score) % 100)
				}
			}
		}
	}
	return values, nil
}

// Delete 删除指定 Key 键名类型数据
func (k *redisKeyKinds) Delete(keys ...string) error {
	if len(keys) == 0 {
		return nil
	}
	if len(keys) == 1 {
		if keys[0] == "" {
			return nil
		}
		return k.client.ZRem(k.ctx, k.ZsetKey(keys[0]), keys[0]).Err()
	}

	var errs error
	for _, key := range keys {
		if key == "" {
			continue
		}
		if err := k.client.ZRem(k.ctx, k.ZsetKey(key), key).Err(); err != nil {
			errs = err
		}
	}
	return errs
}

// Clear 清除所有键名类型数据
func (k *redisKeyKinds) Clear() (errs error) {
	for _, zsetKey := range k.zsetKeys {
		if err := k.client.Del(k.ctx, zsetKey).Err(); err != nil {
			errs = err
		}
	}
	return
}
